#!/usr/bin/php -q
<?	/*
	// File:	webcp.php
	// Purpose:	web-cp CGI Daemon
	// Creation:	2002-02-28
	// Author:	Felix <webmaster@can-host.com>
	*/
//explicitly set error level for people who have changed their php.ini defaults
error_reporting(E_ALL & ~E_NOTICE);
// Change working directory
chdir(dirname(__FILE__));
// Include configuration, system and interface functions
include("../web/config.inc.php");
include("../web/functions.inc.phps");

global $cfg;

// server functions
include("functions.inc.phps");
// Make sure the php CGI has the needed modules
$i = 0;
unset($error);
if (!extension_loaded('mysql'))
	$error[++$i] = "PHP Error: The MySQL module is required (configure PHP '--with-mysql=/usr')\n";
if (!extension_loaded('ftp'))
	$error[++$i] = "PHP Error: The FTP module is required (configure PHP '--enable-ftp')\n";
if (!extension_loaded('zlib'))
	$error[++$i] = "PHP Error: The Zlib module is required (configure PHP '--with-zlib-dir=/usr')\n";
if (!extension_loaded('posix'))
	$error[++$i] = "PHP Error: The POSIX module is required (configure PHP '--enable-posix')\n";
if (!extension_loaded('pcntl'))
	$error[++$i] = "PHP Error: The Process Control module is required (configure PHP '--enable-pcntl')\n";
if (!ini_get("register_argc_argv"))
       $error[++$i] = "Variable register_argc_argv must be set to On inside php.ini\n";

// Disable DB logging until the DB is configured
if (!file_exists($cfg['basedir'].'/config.php'))
{
	$configured = 0; 
	$cfg['dbloglevel'] = -1;
}
else
{
	$configured = 1;
}

// Exit and show error if there is one.
if (isset($error)) {
	webcp_log(0,0,'webcp.php',implode("\n",$error),0,1);
	exit;
}

// Check setup has run before connecting Database
if ($configured) {

	// Connect to the database
	$error = @mysql_connect($cfg['dbhost'], $cfg['dbuser'], $cfg['dbpass']);
	if (!$error)
	{
		webcp_log(0,0,'webcp.php',"Database connection failed, please check your config.php file.",0,1);
		exit;
	} 
	else 
	{
		@mysql_select_db($cfg['dbname']);
	}
}

// Run from root only
if (posix_getuid() != 0) {
	webcp_log(0,0,'webcp.php',"This program must be run as root.",0,1);
	exit;
}
// Check if we are run interactively or with the -d switch; keep on going or become a daemon
if (is_array($_SERVER['argv'])) {
	$args = trim(next($_SERVER['argv']));
}
if ($args == '-d' OR $args == '--daemon') {
	// Fork webcp.php
	$pid = pcntl_fork();
	$echo = 0;
	if ($pid == -1) {
		webcp_log(0,0,'webcp.php',"Error: could not fork.",0,1);
		exit;
	} elseif ($pid) {
		// we are the parent, return 0 to give control to child
		return 0;
	}

	// detach from the controlling terminal
	if (!posix_setsid()) {
		webcp_log(0,0,'webcp.php',"Could not detach from terminal",0,1);
		exit;
	} else
		webcp_log(2,0,'webcp.php',"The web-cp daemon has been started.",0,0);
}
else {
	$echo = 1;
	webcp_log(3,0,'webcp.php',"web-cp ".$cfg['webcp']." daemon usage:
	webcp.php		(run interactively)
	webcp.php -i		(run interactively)
	webcp.php -d, --daemon	(run as daemon)
	webcp.php -h, --help	(display this help notice)

",0,1);
	if ($args == '-h' OR $args == '--help')
		exit;
	else {
		webcp_log(3,0,'webcp.php',"\n\n Running web-cp ".$cfg['webcp']." interactively\n use ./webcp.php -h for help, CTRL-C to exit\n==================================================\n\n",0,1);
	}
}
unset($args);

// Begin initial setup routines
if (!$configured)
{
	unset($error);

	$hostname = exec('hostname');

	if (!file_exists($cfg['basedir']))
	{
		$error[++$i] = "Base directory ".$cfg['basedir']." is incorrect\n"; 
	}
	
	// Create web-cp user and group
	$cfg['prog']['noshell'] = exec('/usr/bin/which false');
	if (!file_exists($cfg['prog']['noshell']))
	{
		$error[++$i] = "Unable to find 'false' command, please configure web-cp manually.\n";
	}
	
	$cfg['prog']['grep'] = exec('/usr/bin/which grep');
	if (!file_exists($cfg['prog']['grep']))
	{
		$error[++$i] = "Unable to find 'grep' command, please configure web-cp manually.\n";
	}
	
	// Check webcp httpd user does not exist before trying to add it
	exec($cfg['prog']['grep'].' '.$cfg['httpd_user'].' /etc/passwd', $output, $return_value);
	if ($return_value != 0)
	{
		$cfg['prog']['gadd'] = exec('/usr/bin/which groupadd');
	
		if (!file_exists($cfg['prog']['gadd']))
		{
			$error[++$i] = "Unable to find 'groupadd' command, please configure web-cp manually.\n";
		}
		else
		{
			exec($cfg['prog']['gadd'].' '.$cfg['httpd_group'], $output, $return_value);
			if ($return_value != 0)
			{
				$error[++$i] = "Unable to create web-cp httpd group, please configure web-cp manually.\n";
			}
		}
	
		$cfg['prog']['uadd'] = exec('/usr/bin/which useradd');
	
		if (!file_exists($cfg['prog']['uadd']))
		{
			$error[++$i] = "Unable to find 'useradd' command, please configure web-cp manually.\n";
		}
		else
		{
			exec($cfg['prog']['uadd']." -c 'web-cp httpd user account' -d ".$cfg['basedir']."/web -g ".$cfg['httpd_group']." -s ".$cfg['prog']['noshell']." ".$cfg['httpd_user'], $output, $return_value);
			if ($return_value != 0)
			{
				$error[++$i] = "Unable to create web-cp httpd user, please configure web-cp manually.\n";
			}
		}		
	}
	
	
	// If Process directory doesn't exist create it
	if (!file_exists('/var/run/webcp'))
	{
		if (!mkdir('/var/run/webcp', 0775))
		{
			$error[++$i] = "Failed to create process directory /var/run/webcp.\n";			
		}
	}
	
	// Setup permissions on process directory
	if (!chown('/var/run/webcp', $cfg['httpd_user']))
	{
		$error[++$i] = "Failed to change ownership on process directory /var/run/webcp.\n";
	}
	if (!chgrp('/var/run/webcp', $cfg['httpd_group']))
	{
		$error[++$i] = "Failed to change ownership on process directory /var/run/webcp.\n";
	}
	
	// Create log files
	if (!touch($cfg['logfile']))
	{
		$error[++$i] = "Failed to create logfile ".$cfg['logfile'].".\n";
	}

	if (!touch($cfg['accesslog']))
	{
		$error[++$i] = "Failed to create logfile ".$cfg['accesslog'].".\n";
	}

	// Setup permissions on log files
	if (!chown($cfg['logfile'], $cfg['httpd_user']))
	{
		$error[++$i] = "Failed to change ownership on log file ".$cfg['logfile'].".\n";
	}
	if (!chgrp($cfg['logfile'], $cfg['httpd_group']))
	{
		$error[++$i] = "Failed to change ownership on log file ".$cfg['logfile'].".\n";
	}

	if (!chown($cfg['accesslog'], $cfg['httpd_user']))
	{
		$error[++$i] = "Failed to change ownership on log file ".$cfg['accesslog'].".\n";
	}
	if (!chgrp($cfg['accesslog'], $cfg['httpd_group']))
	{
		$error[++$i] = "Failed to change ownership on log file ".$cfg['accesslog'].".\n";
	}

	if (isset($error))
	{
		webcp_log(0,0,'webcp.php',"Initial Setup routine failed. ".implode("\n",$error),0,1);
		exit;
	} 

	webcp_log(0,0,'webcp.php',"Setting up web-cp for the first time. Please load http://".$hostname.":".$cfg['port']." in a web browser to begin setup.",0,1);
}

// Log Process ID
file_put_contents($cfg['pid'],getmypid());

// Indicate the backend is up
touch($cfg['basedir'].$cfg['statustag']);

// Initialize vars
$remapmail = FALSE;
$restarthttp = FALSE;
$restartmail = FALSE;
$restartdns = FALSE;
$domainchange = FALSE;
$userchange = FALSE;
$endlessloop = 1;

// Set no timeout (unlimited)
set_time_limit(0);

// Load Modules
load_modules();
//call_hook('server:cfg', $cfg);
$newcfg = array();
call_hook('server:cfg:default', $newcfg);

array_concat($cfg, $newcfg, false);
//$cfg += $newcfg;
call_hook('server:cfg:set', $cfg);

call_hook('server:start', $data);

// Start endless loop
while ($endlessloop++) {

	// check cron.php zombies child process
	if (file_exists($cfg['pid'].'-cron')) {
		$tmp = (int)trim(file_get_contents($cfg['pid'].'-cron'));
		$f_pid = pcntl_waitpid ($tmp, $f_stat, WNOHANG);
		$f_pid = pcntl_waitpid (0, $f_stat, WNOHANG);
	}

	// every 60 seconds, fork cron.php (webcp's scheduler)
	if (!($endlessloop % 60)) {
		webcp_log(3,0,'webcp.php',"Note: Forking cron.php",0,$echo);

		$pid = pcntl_fork();
		if ($pid == -1) {
			webcp_log(0,0,"webcp.php","Fork Error: could not fork webcp.php for cron.php",0,$echo);
		} elseif (!$pid) {
			// cron.php has forked

			// Log Process ID
			$mypid = getmypid();
			file_put_contents($cfg['pid'].'-cron',$mypid);

			// Set no timeout (unlimited)
			set_time_limit(0);

			// include cron script and stay there
			include("cron.php");
			// exit (kill forked process) when cron.php finished
			exit;
		}
	}

	// Check if there is a server tag set
	$tag = commit('read');
	// Remove flag right away to make room.
	commit('remove');

	if ($tag) {

		// Scan database if flag is stop,start,restart,scan
		switch($tag['action'][0]) {
			// change configuration settings
			case 'config':
				parse_str(trim($tag['action'][1]), $config);
				foreach($config AS $key => $val) {

					// Allow all program locations to be changed
					if (strstr($key, "prog--") || strstr($key, "init--")|| strstr($key, "spid--")) {

						// Determine our two keys
						$keys = explode("--", $key);

						// Hash out 2 dimensional arrays (passed as dim1--dim2) for searching/replace
						$key = str_replace("--", "']['", $key);

						// it is not already set to the same value.
						//if (!isset($cfg[$keys[0]][$keys[1]]) || (isset($cfg[$keys[0]][$keys[1]]) && $val != $cfg[$keys[0]][$keys[1]])) {
							// and don't quote number or boolean values
							if (is_numeric($val) || $val == 'true' || $val == 'false') {
								if (!file_edit('/\$cfg\[[\'"]'.$key.'["\']\].*?;/', '$cfg[\''.$key.'\'] = '.$val.';', $cfg['basedir'].'/config.php')) {
									file_edit("/\?".">/", '', $cfg['basedir'].'/config.php');
									file_append("\$cfg['".$key."'] = ".$val.";\n?".">", $cfg['basedir'].'/config.php');
								}
							} else {
								if (!file_edit('/\$cfg\[[\'"]'.$key.'["\']\].*?;/', '$cfg[\''.$key.'\'] = \''.$val.'\';', $cfg['basedir'].'/config.php')) {
									file_edit("/\?".">/", '', $cfg['basedir'].'/config.php');
									file_append("\$cfg['".$key."'] = '".$val."';\n?".">", $cfg['basedir'].'/config.php');
								}
							}
						//}

					// check and see if config is allowed to be changed
					} elseif (preg_match('/^('.$cfg['allowed_cfg'].')$/', $key)) {
						// it is not already set to the same value.
						//if (!isset($cfg[$key]) || (isset($cfg[$key]) && $val != $cfg[$key])) {
							// handle secret key changes (re-encrypt passwords)
							if ($key == 'key') {
								$SQL = 'UPDATE users SET password = ENCODE(DECODE(password, "'.$cfg['key'].'"), "'.$val.'")';
								mysql_query($SQL);
							}
							// and don't quote number or boolian values
							if (is_numeric($val) || $val == 'true' || $val == 'false') {
								if (!file_edit('/\$cfg\[[\'"]'.$key.'["\']\].*?;/', '$cfg[\''.$key.'\'] = '.$val.';', $cfg['basedir'].'/config.php')) {
									file_edit("/\?".">/", '', $cfg['basedir'].'/config.php');
									file_append("\$cfg['".$key."'] = ".$val.";\n?".">", $cfg['basedir'].'/config.php');
								}
							} else {
								if (!file_edit('/\$cfg\[[\'"]'.$key.'["\']\].*?;/', '$cfg[\''.$key.'\'] = \''.$val.'\';', $cfg['basedir'].'/config.php')) {
									file_edit("/\?".">/", '', $cfg['basedir'].'/config.php');
									file_append("\$cfg['".$key."'] = '".$val."';\n?".">", $cfg['basedir'].'/config.php');
								}
							}
						//}
					}
				}

				// load the newly created settings
				include($cfg['basedir'].'/config.php');

				// reload webcp http server
				if ($cfg['httpd_mode'] == 'webcp') {
					if (file_exists($cfg['httpd_pid']))
						posix_kill(trim(file_get_contents($cfg['httpd_pid'])), 1);
				}
				break;
			// create initial config.php
			case 'setup':
				// don't overwrite someone's custom settings
				if (!file_exists($cfg['basedir'].'/config.php')) {

					// write small header
					$newcfgfile = '<'.'?'."\n";
					$newcfgfile .= "/**********************************************\n";
					$newcfgfile .= " * Check web/config.inc.php for all settings. *\n";
					$newcfgfile .= " **********************************************/\n\n";

					// split up single config line and enter each setting into the config.php file
					parse_str(trim($tag['action'][1]), $config);
					foreach($config AS $key => $val) {
						if (preg_match('/^('.$cfg['allowed_cfg'].')$/', $key)) {
							if (is_numeric($val) || $val == 'true' || $val == 'false') {
								// don't quote number or boolian values
								$newcfgfile .= "\$cfg['".$key."'] = ".$val.";\n";
							} else {
								$newcfgfile .= "\$cfg['".$key."'] = '".$val."';\n";
							}
						}
					}
					$newcfgfile .= '?'.'>';

					file_put_contents($cfg['basedir'].'/config.php', $newcfgfile);

					// change config.php to be group readable by webcp-httpd
					if ($user = posix_getpwnam($cfg['httpd_user'])) {
						chgrp($cfg['basedir'].'/config.php', $user['gid']);
						chmod($cfg['basedir'].'/config.php', 0640);
					}
				}
				// load the newly created settings
				include($cfg['basedir'].'/config.php');
				// reload webcp http server
				if ($cfg['httpd_mode'] == 'webcp') {
					if (file_exists($cfg['httpd_pid']))
						posix_kill(trim(file_get_contents($cfg['httpd_pid'])), 1);
				}
				break;
			/*case "databasefill":
				// try to create and propagate the database
				if (@mysql_connect($cfg['dbhost'], $cfg['dbuser'], $cfg['dbpass'])) {
					if (!@mysql_select_db($cfg['dbname'])) {
						// database is missing create it
						mysql_query("CREATE DATABASE " . $cfg['dbname']);
					}
					$dbp = mysql_query("SHOW TABLES FROM " . $cfg['dbname']);
					if (@mysql_num_rows($dbp) === 0) {
						// database is empty, fill it. TODO: maybe we shouldn't rely on mysql
						empty($cfg['dbpass']) ? $tmp = '' : $tmp = "-p" . $cfg['dbpass'];
						exec($cfg['prog']['mysql']." -s -u " . $cfg['dbuser'] . " " . $tmp . " " . $cfg['dbname'] . " < " . $cfg['basedir'] . "/webcp.sql");
						exec($cfg['prog']['mysql']." -s -u " . $cfg['dbuser'] . " " . $tmp . " " . $cfg['dbname'] . " < " . $cfg['basedir'] . "/help-" . $cfg['defaultlang'] . ".sql");
					}
				} else {
					webcp_log(0,0,'webcp.php',"Error:  Could not connect to database server",0,$echo);
				}
				break;*/
			//reload modules
			case 'reload-modules':
				@mysql_close();
				if (!@mysql_connect($cfg['dbhost'], $cfg['dbuser'], $cfg['dbpass'], true)) {
					webcp_log(0,0,'webcp.php',"Error (reload-modules):  Could not connect to database server",0,$echo);
				} elseif (!@mysql_select_db($cfg['dbname'])) {
					webcp_log(0,0,'webcp.php',"Error (reload-modules): Could not select database",0,$echo);
					exit;
				}

				load_modules();
				global $hooks;
				webcp_log(2, 0, 'webcp.php', 'Reloading modules.', 0, $echo);
				break;
			// module wants something done by backend
			case 'module':
				// Get hook to run and data from $extra
				$extra = explode('|', $tag['action'][1]);
				// We should fork here for security, stability and performance
				call_hook('server:commit:'.$extra[0], $extra[1]);
				break;
			// System Reboot
			case 'reboot':
				exec($cfg['prog']['shut']." -r now");

				// Shut webcp.php gracefully: remove pid file and die
				unlink($cfg['pid']);
				exit;
			// stop/start/restart program or network
			case 'start':
			case 'stop':
			case 'restart':
				// more sysv init stuff here.
				webcp_log(3,0,'webcp.php',$tag['action'][0]." service: ".$tag['action'][1],0,$echo);
				if ($tag['action'][0] == 'stop') {
					$cfg['autostart'][$tag['action'][1]] = 'false';
				} else {
					$cfg['autostart'][$tag['action'][1]] = 'true';
				}
				// Moved to modules
				//exec($cfg['init'][$tag['action'][1]]." ".$tag['action'][0]);
				call_hook('server:service:'.$tag['action'][0].':'.$tag['action'][1], $tag['action']);
				call_hook('server:monitor:check', $tag['action']);
				break;

			// Scan databases users and domains for changes to apply
			case 'scan':
				// Connect to database
				@mysql_close();
				if (!@mysql_connect($cfg['dbhost'], $cfg['dbuser'], $cfg['dbpass'], true)) {
					webcp_log(0,0,'webcp.php',"Error (scan):  Could not connect to database server",0,$echo);
					break;
				}
				else {
					// Select correct database and go on.
					if (!@mysql_select_db($cfg['dbname'])) {
						webcp_log(0,0,'webcp.php',"Error (scan):  Could not select database: ".mysql_error(),0,$echo);
						exit;
					}

					// Begin loop on the db check to insure we don't get into a race situation
					do {
						// Check Domains Table
						$dbp = mysql_query("SELECT id FROM domains WHERE action != ''");
						while ($data = mysql_fetch_array($dbp)) {
							$tmp = fetchdata("*","domain",$data['id']);
							//This isn't required, is it?
							/*// seal check (data integrity)
							$hash = $tmp;
							$hash['hash'] = $hash['time'] = $hash['ucount'] = '';
							var_dump($hash);
							$hash = crypt(md5(serialize($hash)), $cfg['key']);
							if ($hash == $tmp['hash']) {*/
								webcp("domain",$tmp);

								// interactive dump
								webcp_log(3,0,'webcp.php',"Domain ".$tmp['id']." ".$tmp['action'],0,$echo);

								// Signal a domain change
								$domainchange = TRUE;
							/*}
							else {
								// suspend domain
								//  $tmp['action'] = 'suspend';

								webcp("domain",$tmp);
								webcp_log(1,$tmp['id'],"system","Invalid Domain Seal -- Domain $tmp[id] $tmp[action]",0, $echo);

								// Signal a domain change
								$domainchange = TRUE;
							}*/
						}

						// Check Users Table
						$dbp = mysql_query("SELECT username, id FROM users WHERE action != ''");
						while ($data = mysql_fetch_array($dbp)) {
							$tmp = fetchdata("*","user",array($data['username'], $data['id']));
							// seal check (data integrity)
							/*$hash = $tmp;
							$hash['hash'] = $hash['time'] = $hash['ucount'] = '';
							$hash = crypt(md5(serialize($hash)), $cfg['key']);
							if ($hash == $tmp['hash']) {*/

								webcp("user",$tmp);

								// interactive dump
								webcp_log(3,0,'webcp.php',"User ".$tmp['username']." ".$tmp['action'],0,$echo);

								// Signal a user change
								$userchange = TRUE;
							/*} else {
								// suspend user
							//	$tmp['action'] = 'suspend';

								webcp("user",$tmp);
								webcp_log(0,$tmp['id'],"system","Invalid User Seal -- User $tmp[username] $tmp[action]",0,$echo);

    		   						// Signal a user change
								$userchange = TRUE;
    		   					}*/
	    		 			}

	    		 			call_hook('server:maillist:update', $data);

	    		 			// Fetch new db info and see if we're still looping
						$dbp = mysql_query("SELECT id FROM domains WHERE action != ''");
						$dbp1 = mysql_query("SELECT username FROM users WHERE action != ''");

						if (mysql_num_rows($dbp) OR mysql_num_rows($dbp1)) $continueloop = true;
						else $continueloop = false;

	         		} while ($continueloop);
	         	}
				break;
			// Backup code - should be moved to backups module.
			//  fork backup or restore script after setting up variables
			case 'backup':
			case 'restore':
				$action = $tag['action'][0];
				if ($action == 'backup') {
					$backupParms = explode("|", $tag['action'][1]);
				} elseif ($action == 'restore') {
					$restoreFiles = explode("|", $tag['action'][1]);
				}
				webcp_log(3,0,'webcp.php',"Note: Forking ".$action.".php",0,$echo);
				$pid = pcntl_fork();
				if ($pid == -1) {
					webcp_log(0,0,"webcp.php","Fork Error: could not fork webcp.php for ".$action.".php",0,$echo);
				} elseif (!$pid) {

					// Log Process ID
					$mypid = getmypid();
					file_put_contents($cfg['pid'].'-'.$action,$mypid);

					// Set no timeout (unlimited)
					set_time_limit(0);

					// include cron script and stay there
					include($action.".php");
					exit;
				}
				break;
			}

		// See if we trapped any signal and update system configuration files
		if ($domainchange) {
			$domainchange = FALSE;
			updatesys("domain");
		}
		if ($userchange) {
			$userchange = FALSE;
			updatesys("user");
		}

		// reload / restart services as appropriate
		reload_services();
	}
	// Sleep for 1 second and loop!
	sleep(1);
}
?>
